package org.sean.framework.text;

import lombok.Data;
import org.sean.framework.code.StatusInfo;
import org.sean.framework.exception.StatusException;
import org.sean.framework.logging.Logger;
import org.sean.framework.util.StringUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 思路：    创建一个FilterSet，枚举了0~65535的所有char是否是某个敏感词开头的状态
 * <p>
 * 判断是否是 敏感词开头
 * |            |
 * 是            不是
 * 获取头节点            OK--下一个字
 * 然后逐级遍历，DFA算法
 *
 * @author xielei
 */
public class WordFilter {
    private static final Logger LOGGER = Logger.newInstance(WordFilter.class);

    private static final WordFilterSet FILTER_SET = new WordFilterSet();
    private static final Map<Integer, WordNode> NODES = new HashMap<>(1024, 1);

    private static final char SIGN = '*';

    static {
        try {
            init();
        } catch (Exception e) {
            throw new StatusException(StatusInfo.serverInitError());
        }
    }

    private WordFilter() {
    }

    /**
     * 初始化
     */
    private static void init() throws IOException {
        Set<String> words;
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(WordFilter.class.getClassLoader().getResourceAsStream("black_words.txt")));
            words = new HashSet<>();
            for (String buf = ""; (buf = br.readLine()) != null; ) {
                if (StringUtil.isEmpty(buf)) {
                    continue;
                }
                words.add(buf);
            }
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (Exception e) {
                LOGGER.printWarnStackTrace(e);
            }
        }
        //获取敏感词
        buildSensitiveWordTree(words);
    }

    /**
     * 构建敏感词树
     */
    private static void buildSensitiveWordTree(Set<String> words) {
        char[] chs;
        int fchar;
        int lastIndex;
        WordNode fnode;
        for (String curr : words) {
            chs = curr.toCharArray();
            fchar = chs[0];
            //没有首字定义
            if (!FILTER_SET.contains(fchar)) {
                //首字标志位
                FILTER_SET.add(fchar);
                fnode = new WordNode(fchar, chs.length == 1);
                NODES.put(fchar, fnode);
            } else {
                fnode = NODES.get(fchar);
                if (!fnode.isLast() && chs.length == 1) {
                    fnode.setLast(true);
                }
            }
            lastIndex = chs.length - 1;
            for (int i = 1; i < chs.length; i++) {
                fnode = fnode.addIfNoExist(chs[i], i == lastIndex);
            }
        }
    }

    /**
     * @param src 源字符串
     * @return 是否包含关键字
     */
    public static boolean hasBlackWords(String src) {
        FilterResult result = filterWords(src, true);
        return result.hasBlackWord;
    }

    public static FilterResult filterWords(String src) {
        return filterWords(src, false);
    }

    /**
     * 过滤关键词
     *
     * @param src         源字符串
     * @param isInterrupt 是否中断
     * @return null 表示还有关键字
     */
    public static FilterResult filterWords(final String src, boolean isInterrupt) {
        FilterResult result = new FilterResult(false, src);
        if (StringUtil.isEmpty(src)) {
            return result;
        }
        StringBuilder words = new StringBuilder();
        char[] chs = src.toCharArray();
        int length = chs.length;
        int currc;
        int k;
        WordNode node;
        for (int i = 0; i < length; i++) {
            currc = chs[i];
            if (!FILTER_SET.contains(currc) || (node = NODES.get(currc)) == null) {
                continue;
            }
            boolean couldMark = false;
            int markNum = -1;
            if (node.isLast()) {
                //单字匹配
                couldMark = true;
                markNum = 0;
            }
            //继续匹配，以长的优先
            k = i;
            while (++k < length) {
                node = node.querySub(chs[k]);
                if (node == null) {
                    break;
                }
                if (node.isLast()) {
                    couldMark = true;
                    markNum = k - i;
                }
            }
            if (couldMark) {
                result.hasBlackWord = true;
                words.append(",").append(src, i, i + markNum + 1);
                if (isInterrupt) {
                    result.blackWords = words.substring(1);
                    LOGGER.warn("敏感词[{}]", result.blackWords);
                    return result;
                }
                for (k = 0; k <= markNum; k++) {
                    chs[k + i] = SIGN;
                }
                i += markNum;
            }
        }
        result.result = new String(chs);
        if (words.length() > 0) {
            result.blackWords = words.substring(1);
            LOGGER.warn("敏感词[{}]", result.blackWords);
        }
        return result;
    }

    /**
     * 过滤结果
     */
    @Data
    public static class FilterResult {
        private String result;
        private boolean hasBlackWord;
        private String blackWords;

        public FilterResult(boolean hasBlackWord, String result) {
            this.hasBlackWord = hasBlackWord;
            this.result = result;
        }
    }
}
